#include "skynet.h"

#include "skynet_timer.h"
#include "skynet_mq.h"
#include "skynet_server.h"
#include "skynet_handle.h"
#include "spinlock.h"

#include <time.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

typedef void (*timer_execute_func)(void *ud, void *arg);

#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK (TIME_NEAR - 1)
#define TIME_LEVEL_MASK (TIME_LEVEL - 1)

struct timer_event {
  uint32_t handle;
  int session;
};

struct timer_node {
  struct timer_node *next;
  uint32_t expire;
};

struct link_list {
  struct timer_node head;
  struct timer_node *tail;
};

struct timer {
  struct link_list near[TIME_NEAR];
  struct link_list t[4][TIME_LEVEL];
  struct spinlock lock;
  uint32_t time;
  uint32_t starttime;
  uint64_t current;
  uint64_t current_point;
};

static struct timer *TI = NULL;

static inline struct timer_node *link_clear(struct link_list *list) {
  struct timer_node *ret = list->head.next;
  list->head.next = 0;
  list->tail = &(list->head);

  return ret;
}
static inline void link(struct link_list *list, struct timer_node *node) {
  list->tail->next = node;
  list->tail = node;
  node->next = 0;
}

static void add_node(struct timer *T, struct timer_node *node) {
  uint32_t time = node->expire;
  uint32_t current_time = T->time;

  if ((time | TIME_NEAR_MASK) == (current_time | TIME_NEAR_MASK)) {
    link(&T->near[time & TIME_NEAR_MASK], node);
  } else {
    int i;
    uint32_t mask = TIME_NEAR << TIME_LEVEL_SHIFT;
    for (i = 0; i < 3; i++) {
      if ((time | (mask - 1)) == (current_time | (mask - 1))) {
        break;
      }
      mask <<= TIME_LEVEL_SHIFT;
    }

    link(&T->t[i][((time >> (TIME_NEAR_SHIFT + i * TIME_LEVEL_SHIFT)) &
                   TIME_LEVEL_MASK)],
         node);
  }
}

static void timer_add(struct timer *T, void *arg, size_t sz, int time) {
  struct timer_node *node =
      (struct timer_node *)skynet_malloc(sizeof(*node) + sz);
  memcpy(node + 1, arg, sz);

  SPIN_LOCK(T);

  node->expire = time + T->time;
  add_node(T, node);

  SPIN_UNLOCK(T);
}

static void move_list(struct timer *T, int level, int idx) {
  struct timer_node *current = link_clear(&T->t[level][idx]);
  while (current) {
    struct timer_node *temp = current->next;
    add_node(T, current);
    current = temp;
  }
}

static void timer_shift(struct timer *T) {
  int mask = TIME_NEAR;
  uint32_t ct = ++T->time;
  if (ct == 0) {
    move_list(T, 3, 0);
  } else {
    uint32_t time = ct >> TIME_NEAR_SHIFT;
    int i = 0;

    while ((ct & (mask - 1)) == 0) {
      int idx = time & TIME_LEVEL_MASK;
      if (idx != 0) {
        move_list(T, i, idx);
        break;
      }
      mask <<= TIME_LEVEL_SHIFT;
      time >>= TIME_LEVEL_SHIFT;
      ++i;
    }
  }
}

static inline void dispatch_list(struct timer_node *current) {
  do {
    struct timer_event *event = (struct timer_event *)(current + 1);
    struct skynet_message message;
    message.source = 0;
    message.session = event->session;
    message.data = NULL;
    message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;

    skynet_context_push(event->handle, &message);

    struct timer_node *temp = current;
    current = current->next;
    skynet_free(temp);
  } while (current);
}

static inline void timer_execute(struct timer *T) {
  int idx = T->time & TIME_NEAR_MASK;

  while (T->near[idx].head.next) {
    struct timer_node *current = link_clear(&T->near[idx]);
    SPIN_UNLOCK(T);
    // dispatch_list don't need lock T
    dispatch_list(current);
    SPIN_LOCK(T);
  }
}

static void timer_update(struct timer *T) {
  SPIN_LOCK(T);

  // try to dispatch timeout 0 (rare condition)
  timer_execute(T);

  // shift time first, and then dispatch timer message
  timer_shift(T);

  timer_execute(T);

  SPIN_UNLOCK(T);
}

static struct timer *timer_create_timer() {
  struct timer *r = (struct timer *)skynet_malloc(sizeof(struct timer));
  memset(r, 0, sizeof(*r));

  int i, j;

  for (i = 0; i < TIME_NEAR; i++) {
    link_clear(&r->near[i]);
  }

  for (i = 0; i < 4; i++) {
    for (j = 0; j < TIME_LEVEL; j++) {
      link_clear(&r->t[i][j]);
    }
  }

  SPIN_INIT(r)

  r->current = 0;

  return r;
}

int skynet_timeout(uint32_t handle, int time, int session) {
  if (time <= 0) {
    struct skynet_message message;
    message.source = 0;
    message.session = session;
    message.data = NULL;
    message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;

    if (skynet_context_push(handle, &message)) {
      return -1;
    }
  } else {
    struct timer_event event;
    event.handle = handle;
    event.session = session;
    timer_add(TI, &event, sizeof(event), time);
  }

  return session;
}

// centisecond: 1/100 second
static void systime(uint32_t *sec, uint32_t *cs) {
  struct timespec ti;
  clock_gettime(CLOCK_REALTIME, &ti);
  *sec = (uint32_t)ti.tv_sec;
  *cs = (uint32_t)(ti.tv_nsec / 10000000);
}

static uint64_t gettime() {
  uint64_t t;
  struct timespec ti;
  clock_gettime(CLOCK_MONOTONIC, &ti);
  t = (uint64_t)ti.tv_sec * 100;
  t += ti.tv_nsec / 10000000;
  return t;
}

void skynet_updatetime(void) {
  uint64_t cp = gettime();
  if (cp < TI->current_point) {
    skynet_error(NULL, "time diff error: change from %lld to %lld", cp,
                 TI->current_point);
    TI->current_point = cp;
  } else if (cp != TI->current_point) {
    uint32_t diff = (uint32_t)(cp - TI->current_point);
    TI->current_point = cp;
    TI->current += diff;
    int i;
    for (i = 0; i < diff; i++) {
      timer_update(TI);
    }
  }
}

uint32_t skynet_starttime(void) { return TI->starttime; }

uint64_t skynet_now(void) { return TI->current; }

void skynet_timer_init(void) {
  TI = timer_create_timer();
  uint32_t current = 0;
  systime(&TI->starttime, &current);
  TI->current = current;
  TI->current_point = gettime();
}

#define NANOSEC 1000000000
#define MICROSEC 1000000

uint64_t skynet_thread_time(void) {
  struct timespec ti;
  clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ti);

  return (uint64_t)ti.tv_sec * MICROSEC +
         (uint64_t)ti.tv_nsec / (NANOSEC / MICROSEC);
}
